`timescale 1ns / 1ps
/*
 Copyright 2020 Sean Xiao, jxzsxsp@qq.com
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

module exu_LSU #(
    parameter [ 31: 0 ] TMR_BASEADDR    = 32'h0200_0000,
    parameter [ 31: 0 ] PLIC_BASEADDR   = 32'h0c00_0000,
    parameter [ 31: 0 ] CPU_BASEADDR    = 32'h8000_0000,
    parameter [ 31: 0 ] MEM_BASEADDR    = 32'h9000_0000,
    parameter [ 31: 0 ] GPIO_BASEADDR   = 32'hf000_0000,
    parameter [ 31: 0 ] UART_BASEADDR   = 32'he000_0000,

    parameter MEM_D_DEEP = 1024,  //memory data depth
    parameter MEM_D_W    = 32,    //memory data width
    parameter MEM_MSK_W  = 4,     //memory data mask width
    parameter MEM_ADDR_W = 32     //memory address width
)
(
    input  sys_clk,
//==============================================================================
    input  i_EXE_vld,
// load & store address PC
    input  [ 31: 0 ] i_D_PC,

    input i_LOAD,
    input  [ 4: 0 ] i_load_instr,    // {rv32i_lbu,rv32i_lb,rv32i_lhu,rv32i_lh,rv32i_lw};

    input  i_STORE,
    input  [ 2: 0 ] i_store_instr,   // {rv32i_sb,rv32i_sh,rv32i_sw};
    input  [ 4: 0 ] i_rd_idx,
    input  [ 31: 0 ] i_rs2_val,
//==============================================================================
    input  [ 31: 0 ] i_GPIO_dina,
    output [ 31: 0 ] o_GPIO_douta,
    output [ 31: 0 ] o_GPIO_ta,

    input  [ 31: 0 ] i_GPIO_dinb,
    output [ 31: 0 ] o_GPIO_doutb,
    output [ 31: 0 ] o_GPIO_tb,

    input  [ 31: 0 ] i_GPIO_dinc,
    output [ 31: 0 ] o_GPIO_doutc,
    output [ 31: 0 ] o_GPIO_tc,

    input  [ 31: 0 ] i_GPIO_dind,
    output [ 31: 0 ] o_GPIO_doutd,
    output [ 31: 0 ] o_GPIO_td,

//==============================================================================
    output txd_start,
    output [ 7: 0 ] txd_data,
    input  txd_done,
//==============================================================================
    output [ 31: 0 ] o_sft_int_v,
    output [ 31: 0 ] o_timer_l,
    output [ 31: 0 ] o_timer_h,

    input  [ 31: 0 ] i_timer_l,
    input  [ 31: 0 ] i_timer_h,

    output [ 31: 0 ] o_tcmp_l,
    output [ 31: 0 ] o_tcmp_h,

    output [ 1: 0 ] o_timer_valid,

    output [ 31: 0 ] o_tm_ctrl,
//==============================================================================
    output o_gpio_irq,
//==============================================================================
    output o_CPU_cs,
    output [ 31: 0 ] o_CPU_PC,
    input  [ 31: 0 ] i_CPU_load_data,

    output o_ls_need,
    output o_ls_rdy,

    output o_rd_wen,
    output [ 4: 0 ] o_wb_rd_idx,
    output reg[ 31: 0 ] o_wb_data,

    input  i_cpu_reset,
    input  rst_n
);

wire [31: 0] cpu_data_in = i_rs2_val << {i_D_PC[1:0],3'b000};
//==============================================================================
// Memory section
wire mem_cs = ( i_D_PC[ 31: 16 ] == MEM_BASEADDR[ 31: 16 ] );
reg mem_we;
reg [ 3: 0 ] mem_wem;          // memory mask
wire [ 31: 0 ] mem_dout;        // memory dout
wire [ 31: 0 ] mem_addr_out;    // not at all
//wire           mem_init_rdy;
//==============================================================================
// GPIO section
wire [ 31: 0 ] rb_GPIO_d;
wire GPIO_cs = ( i_D_PC[ 31: 16 ] == GPIO_BASEADDR[ 31: 16 ] ) ? 1'b1 : 1'b0;

wire GPIO_we;
wire [ 3: 0 ] GPIO_wem; // gpio mask
//==============================================================================
wire UART_cs = ( i_D_PC[ 31: 16 ] == UART_BASEADDR[ 31: 16 ] ) ? 1'b1 : 1'b0;
wire [ 31: 0 ] o_UART_dout;
//==============================================================================
wire PLIC_cs = ( i_D_PC[ 31: 16 ] == PLIC_BASEADDR[ 31: 16 ] ) ? 1'b1 : 1'b0;
wire [ 31: 0 ] o_PLIC_dout;
//==============================================================================

wire t_sft_cs   = ( i_D_PC[ 31: 16 ] == TMR_BASEADDR[ 31: 16 ] ) ? 1'b1 : 1'b0;
wire sft_cs     = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 0 ) );
wire tm_ctrl_cs = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 1 ) );
wire t_cs0      = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 2 ) );
wire t_cs1      = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 3 ) );
wire tcmp_cs0   = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 4 ) );
wire tcmp_cs1   = t_sft_cs & ( ( ~i_D_PC[ 12 ] ) & ( i_D_PC[ 5: 2 ] == 5 ) );

wire fpga_ver_cs   = t_sft_cs & ( i_D_PC[ 12 ] & ( i_D_PC[ 5: 2 ] == 0 ) );
wire fpga_test_cs  = t_sft_cs & ( i_D_PC[ 12 ] & ( i_D_PC[ 5: 2 ] == 1 ) );

wire [31:0] fpga_ver = 32'h0000_0201;
reg [31:0] fpga_test = 32'h0000_0000;

/*always @ (posedge sys_clk)  
if ( mem_we & fpga_test_cs)
begin
    fpga_test <= i_rs2_val;
end*/




always @ (posedge sys_clk)  
if ( fpga_test_cs )
begin
      if (mem_wem[0])
         fpga_test[ 7 : 0   ] <= cpu_data_in[ 7  : 0  ] ;
      if (mem_wem[1])
         fpga_test[ 15 : 8  ] <= cpu_data_in[ 15 : 8  ] ;
      if (mem_wem[2])
         fpga_test[ 23 : 16 ] <= cpu_data_in[ 23 : 16 ] ;
      if (mem_wem[3])
         fpga_test[ 31 : 24 ] <= cpu_data_in[ 31 : 24 ] ;
end
//==============================================================================
//assign o_CPU_cs = ( i_D_PC[ 31: 16 ] == CPU_BASEADDR[ 31: 16 ] ) ? i_LOAD & i_EXE_vld : 1'b0;
reg CPU_cs = 0;
always @ (*)
if(i_EXE_vld)
        CPU_cs = ( i_D_PC[ 31: 16 ] == CPU_BASEADDR[ 31: 16 ] ) ? i_LOAD : 1'b0;

assign o_CPU_cs = CPU_cs;
//assign o_CPU_cs = ( i_D_PC[ 31: 16 ] == CPU_BASEADDR[ 31: 16 ] ) ? i_LOAD : 1'b0;
assign o_CPU_PC = { i_D_PC[ 31: 2 ], 2'b00 };


// lock current data address
reg [ 1: 0 ] data_sft_r = 0;
always @ (posedge sys_clk)
if(o_CPU_cs & i_EXE_vld)
    data_sft_r <= i_D_PC[1:0];

wire [ 4: 0 ] data_sft = {data_sft_r[ 1: 0 ], 3'b000};

//wire [ 4: 0 ] data_sft = {i_D_PC[ 1: 0 ], 3'b000};
wire [ 31: 0 ] o_CPU_dout = i_CPU_load_data >> data_sft;

//==============================================================================
wire [ 31: 0 ] ls_rb_d_t_sft = sft_cs ? o_sft_int_v :
                             ( tm_ctrl_cs ? o_tm_ctrl :
                             ( t_cs0 ? i_timer_l :
                             ( t_cs1 ? i_timer_h :
                             ( fpga_ver_cs ? fpga_ver :
                             ( fpga_test_cs ? (fpga_test>>{i_D_PC[1:0],3'b000}) :
                             ( tcmp_cs0 ? o_tcmp_l :
                             ( tcmp_cs1 ? o_tcmp_h : o_CPU_dout ) ) ) ) )));
wire [ 31: 0 ] ls_rb_d = mem_cs ? mem_dout : ( GPIO_cs ? rb_GPIO_d : ( UART_cs ? o_UART_dout : ls_rb_d_t_sft ) );

//wire [ 31: 0 ] ls_rb_d = mem_cs ? mem_dout : ( GPIO_cs ? rb_GPIO_d : ( UART_cs ? o_UART_dout : o_CPU_dout ) );
always@( * )
begin
    mem_we <= 1'b0;
    mem_wem <= 4'b0;
    o_wb_data <= 32'b0;

    if ( i_LOAD )
    begin //&mem_init_rdy
        case ( i_load_instr )    // i_load_instr  ={rv32i_lbu, rv32i_lb, rv32i_lhu, rv32i_lh, rv32i_lw};
            5'b00001:
            begin  //rv32i_lw
                o_wb_data <= ls_rb_d;
            end
            5'b00010:
            begin //rv32i_lh
                o_wb_data <= { { 16{ ls_rb_d[ 15 ] } }, ls_rb_d[ 15: 0 ] };
            end
            5'b00100:
            begin //rv32i_lhu
                o_wb_data <= { { 16{ 1'b0 } }, ls_rb_d[ 15: 0 ] };
            end
            5'b01000:
            begin //rv32i_lb
                o_wb_data <= { { 24{ ls_rb_d[ 7 ] } }, ls_rb_d[ 7: 0 ] };
            end
            5'b10000:
            begin //rv32i_lbu
                o_wb_data <= { { 24{ 1'b0 } }, ls_rb_d[ 7: 0 ] };
            end
            default: ;

        endcase
    end

    if ( i_STORE )
    begin //&mem_init_rdy
        mem_we <= 1'b1;
        


        case ( i_store_instr )    //i_store_instr ={rv32i_sb, rv32i_sh, rv32i_sw};
            3'b001:
            begin  //rv32i_sw
                mem_wem <= 4'b1111; 
            end
            3'b010:
            begin //rv32i_sh
                mem_wem <= 4'b0011 << {i_D_PC[1],1'b0}; 
            end
            3'b100:
            begin //rv32i_sb
                mem_wem <= 4'b0001 << i_D_PC[1:0]; 
            end
            default:
                mem_wem <= 4'b0;

        endcase
    end
end

//==============================================================================
D_sram
#(
    .MEM_D_DEEP ( MEM_D_DEEP ),
    .MEM_D_W    ( MEM_D_W ),
    .MEM_MSK_W  ( MEM_MSK_W ),
    .MEM_ADDR_W ( MEM_ADDR_W )
)
D_sram_inst
(
    .clk    ( sys_clk ),
    .rst_n  ( rst_n ),

    .din    ( cpu_data_in ),
    .addr   ( i_D_PC ),
    .dout   ( mem_dout ),

    .cs     ( mem_cs ),
    .we     ( mem_we ),
    .wem    ( mem_wem ),

    //.mem_init_rdy   (mem_init_rdy),
    .o_D_PC ( mem_addr_out )  //not at all
);

//==============================================================================
assign GPIO_we = mem_we;
assign GPIO_wem = mem_wem;

yue_GPIO
#(
    .GPIO_DEEP      ( 8 ),          // register number
    .GPIO_W         ( MEM_D_W ),
    .GPIO_MSK_W     ( MEM_MSK_W ),
    .GPIO_ADDR_W    ( MEM_ADDR_W )
) yue_GPIO_inst
(
    .clk            ( sys_clk ),
    .rst_n          ( rst_n ),

    .i_ls_GPIO_din  ( cpu_data_in ),
    .o_rb_GPIO_dout ( rb_GPIO_d ),

    .i_addr         ( i_D_PC ),
    .i_cs           ( GPIO_cs ),
    .i_we           ( GPIO_we ),
    .i_wem          ( GPIO_wem ),

    .i_GPIO_dina    ( i_GPIO_dina ),
    .o_GPIO_douta   ( o_GPIO_douta ),
    .o_GPIO_ta      ( o_GPIO_ta ),

    .i_GPIO_dinb    ( i_GPIO_dinb ),
    .o_GPIO_doutb   ( o_GPIO_doutb ),
    .o_GPIO_tb      ( o_GPIO_tb ),

    .i_GPIO_dinc    ( i_GPIO_dinc ),
    .o_GPIO_doutc   ( o_GPIO_doutc ),
    .o_GPIO_tc      ( o_GPIO_tc ),

    .i_GPIO_dind    ( i_GPIO_dind ),
    .o_GPIO_doutd   ( o_GPIO_doutd ),
    .o_GPIO_td      ( o_GPIO_td )
);
//===============================================================================
wire UART_we = mem_we;
wire UART_wem = mem_wem;

yue_UART yue_UART_inst
(
    .clk        ( sys_clk ),

    .i_PERI_din ( cpu_data_in ),
    .o_PERI_dout( o_UART_dout ),

    .i_addr     ( i_D_PC ),
    .i_cs       ( UART_cs ),
    .i_we       ( UART_we ),
    .i_wem      ( UART_wem ),

    .txd_start  ( txd_start ),
    .txd_data   ( txd_data ),
    .txd_done   ( txd_done ),

    .rst_n      ( rst_n )
);


//===============================================================================

wire tmr_sft_we = mem_we;

yue_timer_lsu yue_timer_lsu_inst
(
    .clk            ( sys_clk ),

    .i_sft_timer_din( cpu_data_in ),
    .i_tmr_sft_we   ( tmr_sft_we ),

    .i_tm_ctrl_cs   ( tm_ctrl_cs ),
    .o_tm_ctrl      ( o_tm_ctrl ),

    .i_sft_cs       ( sft_cs ),
    .o_sft_int_v    ( o_sft_int_v ),

    .i_tcs0         ( t_cs0 ),
    .o_timer_l      ( o_timer_l ),

    .i_tcs1         ( t_cs1 ),
    .o_timer_h      ( o_timer_h ),

    .i_tcmp_cs0     ( tcmp_cs0 ),
    .o_tcmp_l       ( o_tcmp_l ),

    .i_tcmp_cs1     ( tcmp_cs1 ),
    .o_tcmp_h       ( o_tcmp_h ),

    .o_timer_valid  ( o_timer_valid ),

    .rst_n          ( rst_n & (!i_cpu_reset))
);


//===============================


assign o_rd_wen = i_LOAD; //o_wb_need
assign o_ls_need = i_LOAD | i_STORE;
assign o_ls_rdy = 1;

assign o_wb_rd_idx = i_rd_idx;
//===============================================================================

endmodule
